package token

import (
	"crypto/rand"
	"encoding/base32"
	"encoding/binary"
	"io"
	"time"

	"github.com/pkg/errors"
)

var (
	ErrExpired   = errors.New("token expired")
	ErrMalformed = errors.New("token malformed")

	b32 = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
)

type Service struct {
	key uint64
}

func (s *Service) Create(tType byte, uid uint64, exp time.Time) (tokenString string) {
	var tBytes [25]byte
	_, _ = io.ReadFull(rand.Reader, tBytes[17:25])
	key := binary.BigEndian.Uint64(tBytes[17:25]) ^ s.key
	binary.BigEndian.PutUint64(tBytes[9:17], uint64(exp.UnixNano())^key)
	binary.BigEndian.PutUint64(tBytes[1:9], uid^key)
	tBytes[0] = tType
	tBytes[0], tBytes[10] = tBytes[10], tBytes[0]
	return b32.EncodeToString(tBytes[:])
}

func (s *Service) Parse(tokenString string) (*Token, error) {
	tBytes, err := b32.DecodeString(tokenString)
	if err != nil {
		return nil, errors.Wrap(ErrMalformed, err.Error())
	}

	if len(tBytes) != 25 {
		return nil, errors.Wrap(ErrMalformed, "length")
	}

	tBytes[0], tBytes[10] = tBytes[10], tBytes[0]
	var (
		rid = binary.BigEndian.Uint64(tBytes[17:25])
		key = rid ^ s.key
		eid = binary.BigEndian.Uint64(tBytes[9:17]) ^ key
		uid = binary.BigEndian.Uint64(tBytes[1:9]) ^ key
		typ = tBytes[0]
	)

	const sec = uint64(time.Second)
	var exp = time.Unix(int64(eid/sec), int64(eid%sec))
	var token = &Token{RID: rid, UID: uid, Exp: exp, Typ: typ}

	if exp.Before(time.Now()) {
		return token, errors.Wrap(ErrExpired, time.Since(exp).String())
	}
	return token, nil
}

type Token struct {
	UID uint64    `json:"uid,omitempty"` //用户编号
	Exp time.Time `json:"exp,omitempty"` //过期时间
	Typ byte      `json:"typ,omitempty"` //令牌类型
	RID uint64    `json:"rid,omitempty"` //随机码
}

func (token Token) IsExpired() bool {
	return token.Exp.Before(time.Now())
}
